Skip to content

Conversation

@Zeroto521
Copy link
Contributor

to fix #1062

Introduces the test_evaluate function to verify correct evaluation of various expressions using Model.getVal, including arithmetic and trigonometric operations, and checks for TypeError on invalid input.
Introduced _evaluate methods to Term, Expr, and all GenExpr subclasses for consistent evaluation of expressions and variables. Refactored Solution and Model to use these methods, simplifying and unifying value retrieval for variables and expressions. Cleaned up class definitions and improved hashing and equality for Term and Variable.
Replaced explicit loop with flat iterator for evaluating matrix expressions in Solution.getVal, improving performance and code clarity.
Introduces a new test 'test_evaluate' to verify correct evaluation of matrix variable division and summation in the model.
Eliminated special handling for MatrixExpr and MatrixGenExpr in Solution.__getitem__, simplifying the method to only evaluate single expressions. This change streamlines the code and removes unnecessary numpy array construction.
Renamed 'vars' to 'vartuple' and '_hash' to 'hashval' in the Term class for clarity. Updated all references and methods to use the new attribute names, improving code readability and consistency.
Changed internal evaluation methods in expression classes from float to double for improved numerical precision. Updated type declarations and imports accordingly.
Added type annotations to the Term constructor and specified types for class attributes. This improves code clarity and type safety.
Changed all _evaluate methods in expression classes from cdef to cpdef to make them accessible from Python. Moved the definition of terms and _evaluate to the Expr class in scip.pxd, and refactored Variable to inherit from Expr in scip.pxd, consolidating class member definitions. These changes improve the interface and maintainability of expression evaluation.
import math
from typing import TYPE_CHECKING

from pyscipopt.scip cimport Variable, Solution
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could let IDE know what Variable and Solution are.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is only relevant for people working with stuff inside expr.pxi, right?

include "matrix.pxi"

if TYPE_CHECKING:
double = float
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For IDE

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Ensures that MatrixExpr._evaluate returns a numpy ndarray when appropriate. Adds a test to verify the return type of getVal for matrix variables.
Deleted the _evaluate method from the Solution class in the scip.pyi stub file, as it is no longer needed. Also added TYPE_CHECKING to the typing imports.
Applied the @disjoint_base decorator to the Term and UnaryExpr classes in scip.pyi to clarify their base class relationships. This may improve type checking or class hierarchy handling.
Appended '# noqa: F401' to the 'ClassVar' import to suppress linter warnings about unused imports in scip.pyi.
def __matmul__(self, other):
return super().__matmul__(other).view(MatrixExpr)

def _evaluate(self, Solution sol) -> NDArray[np.float64]:
Copy link
Contributor Author

@Zeroto521 Zeroto521 Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np.ndarray is not a cdef class, so it can't convert MatrixExpr to a cdef class.
And in MatrixExpr, _evaluate is a pure Python function.

Replaces the @np.vectorize-decorated _evaluate function with an np.frompyfunc-based implementation for evaluating expressions with solutions. This change may improve compatibility and performance when applying _evaluate to arrays.
Refactored the _evaluate method in MatrixExpr to always return the result as a NumPy ndarray, removing the conditional type check.
@codecov
Copy link

codecov bot commented Jan 9, 2026

Codecov Report

❌ Patch coverage is 62.79070% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.12%. Comparing base (5ceac82) to head (7cd196a).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
src/pyscipopt/expr.pxi 62.50% 12 Missing ⚠️
src/pyscipopt/matrix.pxi 25.00% 3 Missing ⚠️
src/pyscipopt/scip.pxi 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1148      +/-   ##
==========================================
+ Coverage   55.07%   55.12%   +0.04%     
==========================================
  Files          24       24              
  Lines        5420     5448      +28     
==========================================
+ Hits         2985     3003      +18     
- Misses       2435     2445      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.


def test_evaluate():
m = Model()
x = m.addVar(lb=1, ub=1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this please be 2, instead? Just to be safer that it works (a bit paranoid, I know).

import math
from typing import TYPE_CHECKING

from pyscipopt.scip cimport Variable, Solution
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is only relevant for people working with stuff inside expr.pxi, right?

include "matrix.pxi"

if TYPE_CHECKING:
double = float
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

cpdef double _evaluate(self, Solution sol):
cdef double res = 1
cdef Variable i
for i in self.vartuple:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for my ignorance, but what is a vartuple here? The idea I have in my mind doesn't fit with the result computation below, but my idea is pretty ignorant, admittedly.

cdef double coef
cdef Term term
for term, coef in self.terms.items():
res += coef * term._evaluate(sol)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this count for when there are higher degrees?

cdef GenExpr child
cdef double coef
for child, coef in zip(self.children, self.coefs):
res += coef * child._evaluate(sol)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

return res.view(MatrixExprCons)


_evaluate = np.frompyfunc(lambda expr, sol: expr._evaluate(sol), 2, 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be here, right?

Comment on lines 266 to 269
def __truediv__(self,other):
if _is_number(other):
f = 1.0/float(other)
return f * self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end, this is a much bigger change than I had anticipated. Shouldn't the fix rather be something in here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: can't call getVal from a matrix expression

2 participants